// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build helenos

package os

import (
	"runtime"
	"syscall"
	"time"
	"io"
)

// Flags to Open wrapping those of the underlying system.
// HelenOS implements all but O_SYNC. Do not depend on O_SYNC for proper function.
const (
	O_RDONLY int = syscall.O_RDONLY // open the file read-only.
	O_WRONLY int = syscall.O_WRONLY // open the file write-only.
	O_RDWR   int = syscall.O_RDWR   // open the file read-write.
	O_APPEND int = syscall.O_APPEND // append data to the file when writing.
	O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
	O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist
	O_SYNC   int = 0                // open for synchronous I/O.
	O_TRUNC  int = syscall.O_TRUNC  // if possible, truncate file when opened.
)

// Seek whence values.
const (
	SEEK_SET int = 0 // seek relative to the origin of the file
	SEEK_CUR int = 1 // seek relative to the current offset
	SEEK_END int = 2 // seek relative to the end
)

// The defined file mode bits are the most significant bits of the FileMode.
// The nine least-significant bits are the standard Unix rwxrwxrwx permissions.
// The values of these bits should be considered part of the public API and
// may be used in wire protocols or disk representations: they must not be
// changed, although new bits might be added.
const (
	// The single letters are the abbreviations
	// used by the String method's formatting.
	ModeDir        FileMode = 1 << (32 - 1 - iota) // d: is a directory
	ModeAppend                                     // a: append-only
	ModeExclusive                                  // l: exclusive use
	ModeTemporary                                  // T: temporary file (not backed up)
	ModeSymlink                                    // L: symbolic link
	ModeDevice                                     // D: device file
	ModeNamedPipe                                  // p: named pipe (FIFO)
	ModeSocket                                     // S: Unix domain socket
	ModeSetuid                                     // u: setuid
	ModeSetgid                                     // g: setgid
	ModeCharDevice                                 // c: Unix character device, when ModeDevice is set
	ModeSticky                                     // t: sticky

	// Mask for the type bits. For regular files, none will be set.
	ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice

	ModePerm FileMode = 0777 // permission bits
)

// DevNull is the name of the operating system's ``null device.''
// FIXME: check if there is such a file on HelenOS
const DevNull = "/dev/null"

// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
// standard output, and standard error file descriptors.
//
// Note that these files do not refer to actual filesystem nodes.
// They should not be used as such.
var (
	Stdin  = NewFile(uintptr(syscall.Stdin), "stdin")
	Stdout = NewFile(uintptr(syscall.Stdout), "stdout")
	Stderr = NewFile(uintptr(syscall.Stderr), "stderr")
)

// File represents an open file descriptor.
type File struct {
	*file
}

// file is the real representation of *File.
// The extra level of indirection ensures that no clients of os
// can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor.
type file struct {
	fd      int
	name    string
	dir     *syscall.DIR // nil unless directory being read
}

// NewFile returns a new File with the given file descriptor and name.
func NewFile(fd uintptr, name string) *File {
	fdi := int(fd)
	if fdi < 0 {
		return nil
	}
	
	// Remove trailing slashes
	for i := len(name) - 1; i > 0 && name[i] == PathSeparator; i-- {
		name = name[:i]
	}
	
	f := &File{&file{fd: fdi, name: name}}
	runtime.SetFinalizer(f.file, (*file).close)
	return f
}

// Open opens the named file for reading.  If successful, methods on
// the returned file can be used for reading; the associated file
// descriptor has mode O_RDONLY.
// If there is an error, it will be of type *PathError.
func Open(name string) (file *File, err error) {
	return OpenFile(name, O_RDONLY, 0)
}

// Create creates the named file mode 0666 (before umask), truncating
// it if it already exists.  If successful, methods on the returned
// File can be used for I/O; the associated file descriptor has mode
// O_RDWR.
// If there is an error, it will be of type *PathError.
func Create(name string) (file *File, err error) {
	return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

// OpenFile is the generalized open call; most users will use Open
// or Create instead.  It opens the named file with specified flag
// (O_RDONLY etc.) and perm, (0666 etc.) if applicable.  If successful,
// methods on the returned File can be used for I/O.
// If there is an error, it will be of type *PathError.
func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
	r, e := syscall.Open(name, flag)
	if e != nil {
		return nil, &PathError{"open", name, e}
	}
	return NewFile(uintptr(r), name), nil
}

// Name returns the name of the file as presented to Open.
func (f *File) Name() string {
	return f.name
}

// Read reads up to len(b) bytes from the File.
// It returns the number of bytes read and an error, if any.
// EOF is signaled by a zero count with err set to io.EOF.
func (f *File) Read(b []byte) (n int, err error) {
	if f == nil {
		return 0, ErrInvalid
	}
	
	if n, err = syscall.ReadAll(f.fd, b); err != nil {
		err = &PathError{"read", f.name, err}
	}
	
	if n == 0 && len(b) > 0 && err == nil{
		err = io.EOF
	}
	
	return
}

// ReadAt reads len(b) bytes from the File starting at byte offset off.
// It returns the number of bytes read and the error, if any.
// ReadAt always returns a non-nil error when n < len(b).
// At end of file, that error is io.EOF.
func (f *File) ReadAt(b []byte, off int64) (n int, err error) {
	if f == nil {
		return 0, ErrInvalid
	}
	
	if _, err = syscall.LSeek(f.fd, off, SEEK_SET); err != nil {
		return 0, &PathError{"seek", f.name, err}
	}
	
	if n, err = syscall.ReadAll(f.fd, b); err != nil {
		err = &PathError{"read", f.name, err}
	}
	
	if n < len(b) && err == nil {
		err = io.EOF
	}
	
	return
}

// Write writes len(b) bytes to the File.
// It returns the number of bytes written and an error, if any.
// Write returns a non-nil error when n != len(b).
func (f *File) Write(b []byte) (n int, err error) {
	if f == nil {
		return 0, ErrInvalid
	}
	
	if n, err = syscall.WriteAll(f.fd, b); err != nil {
		err = &PathError{"write", f.name, err}
	}
	
	return
}

// WriteAt writes len(b) bytes to the File starting at byte offset off.
// It returns the number of bytes written and an error, if any.
// WriteAt returns a non-nil error when n != len(b).
func (f *File) WriteAt(b []byte, off int64) (n int, err error) {
	if f == nil {
		return 0, ErrInvalid
	}
	
	if _, err = syscall.LSeek(f.fd, off, SEEK_SET); err != nil {
		return 0, &PathError{"seek", f.name, err}
	}
	
	if n, err = syscall.WriteAll(f.fd, b); err != nil {
		err = &PathError{"write", f.name, err}
	}
	
	return
}

// Seek sets the offset for the next Read or Write on file to offset, interpreted
// according to whence: 0 means relative to the origin of the file, 1 means
// relative to the current offset, and 2 means relative to the end.
// It returns the new offset and an error, if any.
func (f *File) Seek(offset int64, whence int) (ret int64, err error) {
	if ret, err = syscall.LSeek(f.fd, offset, whence); err != nil {
		return 0, &PathError{"seek", f.name, err}
	}
	return ret, nil
}

// WriteString is like Write, but writes the contents of string s rather than
// an array of bytes.
func (f *File) WriteString(s string) (ret int, err error) {
	if f == nil {
		return 0, ErrInvalid
	}
	return f.Write([]byte(s))
}

// Chdir changes the current working directory to the file,
// which must be a directory.
// If there is an error, it will be of type *PathError.
func (f *File) Chdir() error {
	if e := syscall.Chdir(f.name); e != nil {
		return &PathError{"chdir", f.name, e}
	}
	return nil
}

// Fd returns the integer file descriptor referencing the open file.
func (f *File) Fd() uintptr {
	if f == nil {
		return ^(uintptr(0))
	}
	return uintptr(f.fd)
}

// Close closes the File, rendering it unusable for I/O.
// It returns an error, if any.
func (f *File) Close() error {
	return f.file.close()
}

func (file *file) close() error {
	if file == nil || file.fd < 0 {
		return ErrInvalid
	}
	var err error
	if e := syscall.Close(file.fd); e != nil {
		err = &PathError{"close", file.name, e}
	}

	if file.dir != nil {
		if e := syscall.CloseDir(file.dir); e != nil && err == nil {
			err = &PathError{"closedir", file.name, syscall.GetErrno()}
		}
	}

	file.fd = -1 // so it can't be closed again

	// no need for a finalizer anymore
	runtime.SetFinalizer(file, nil)
	return err
}

// Stat returns the FileInfo structure describing file.
// If there is an error, it will be of type *PathError.
func (f *File) Stat() (fi FileInfo, err error) {
	var stat syscall.StatT
	if err := syscall.FStat(f.fd, &stat); err != nil {
		return nil, &PathError{"stat", f.name, err}
	}
	return &fileStat{f.name, stat}, nil
}

func (f *File) Sync() error {
	err := syscall.FSync(f.fd)
	if err != nil {
		return NewSyscallError("fsync", err)
	}
	return nil
}

// basename removes trailing slashes and the leading directory name from path name
func basename(name string) string {
	i := len(name) - 1
	// Remove trailing slashes
	for ; i > 0 && name[i] == '/'; i-- {
		name = name[:i]
	}
	// Remove leading directory name
	for i--; i >= 0; i-- {
		if name[i] == '/' {
			name = name[i+1:]
			break
		}
	}

	return name
}

func (f *File) Readdir(n int) (fi []FileInfo, err error) {
	if f.dir == nil {
		f.dir, err = syscall.OpenDir(f.name)
		if err != nil {
			return
		}
	}
	
	cap := n
	
	if n <= 0 {
		cap = 16
	}
	
	fi = make([]FileInfo, 0, n)
	
	if n == 0 {
		// Make sure the loop executes.
		n = -1
	}
	
	for ; n != 0; n-- {
		entry := syscall.ReadDir(f.dir)
		if entry == "" {
			break
		}
		if entry == "." || entry == ".." {
			continue
		}
			
		var info FileInfo
		
		info, err = Lstat(f.name + string(PathSeparator) + entry)
		if err != nil {
			break
		}
		
		fi = append(fi, info)		
	}
	
	if err == nil && len(fi) == 0 {
		// HelenOS libc doesn't give us any information.
		err = io.EOF
	}

	return
}

// Readdirnames reads and returns a slice of names from the directory f.
//
// If n > 0, Readdirnames returns at most n names. In this case, if
// Readdirnames returns an empty slice, it will return a non-nil error
// explaining why. At the end of a directory, the error is io.EOF.
//
// If n <= 0, Readdirnames returns all the names from the directory in
// a single slice. In this case, if Readdirnames succeeds (reads all
// the way to the end of the directory), it returns the slice and a
// nil error. If it encounters an error before the end of the
// directory, Readdirnames returns the names read until that point and
// a non-nil error.
func (f *File) Readdirnames(n int) (names []string, err error) {
	if f.dir == nil {
		f.dir, err = syscall.OpenDir(f.name)
		if err != nil {
			return
		}
	}
	
	cap := n
	
	if n <= 0 {
		cap = 16
	}
	
	names = make([]string, 0, n)
	
	if n == 0 {
		// Make sure the loop executes.
		n = -1
	}
	
	for ; n != 0; n-- {
		entry := syscall.ReadDir(f.dir)
		if entry == "" {
			break
		}
		if entry == "." || entry == ".." {
			continue
		}
		
		names = append(names, entry)		
	}
	
	if err == nil && len(names) == 0 {
		// HelenOS libc doesn't give us any information.
		err = io.EOF
	}

	return
}

// A FileInfo describes a file and is returned by Stat and Lstat
type FileInfo interface {
	Name() string       // base name of the file
	Size() int64        // length in bytes for regular files; system-dependent for others
	Mode() FileMode     // file mode bits
	ModTime() time.Time // modification time
	IsDir() bool        // abbreviation for Mode().IsDir()
	Sys() interface{}   // underlying data source (can return nil)
}

// A FileMode represents a file's mode and permission bits.
// The bits have the same definition on all systems, so that
// information about files can be moved from one system
// to another portably.  Not all bits apply to all systems.
// The only required bit is ModeDir for directories.
type FileMode uint32

func (m FileMode) String() string {
	const str = "dalTLDpSugct"
	var buf [32]byte // Mode is uint32.
	w := 0
	for i, c := range str {
		if m&(1<<uint(32-1-i)) != 0 {
			buf[w] = byte(c)
			w++
		}
	}
	if w == 0 {
		buf[w] = '-'
		w++
	}
	const rwx = "rwxrwxrwx"
	for i, c := range rwx {
		if m&(1<<uint(9-1-i)) != 0 {
			buf[w] = byte(c)
		} else {
			buf[w] = '-'
		}
		w++
	}
	return string(buf[:w])
}

// IsDir reports whether m describes a directory.
// That is, it tests for the ModeDir bit being set in m.
func (m FileMode) IsDir() bool {
	return m&ModeDir != 0
}

// Perm returns the Unix permission bits in m.
func (m FileMode) Perm() FileMode {
	return m & ModePerm
}

// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
type fileStat struct {
	name    string
	sys     syscall.StatT
}

func (fs *fileStat) Name() string           { return fs.name }
func (fs *fileStat) Size() int64            { return int64(fs.sys.Size) }
func (fs *fileStat) Mode() FileMode         { if fs.sys.IsDirectory { return ModeDir } else { return 0 } }
func (fs *fileStat) ModTime() time.Time     { var zero time.Time; return zero } // FIXME
func (fs *fileStat) IsDir() bool            { return fs.sys.IsDirectory }
func (fs *fileStat) Sys() interface{}       { return fs.sys }

func SameFile(fi1, fi2 FileInfo) bool {
	fs1, ok1 := fi1.(*fileStat)
	fs2, ok2 := fi2.(*fileStat)
	if !ok1 || !ok2 {
		return false
	}
	
	return fs1.sys.FsHandle == fs2.sys.FsHandle &&
		fs1.sys.ServiceID == fs2.sys.ServiceID &&
		fs1.sys.Index == fs2.sys.Index
}


